package com.itesm.distribuidos.tarea4;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.StringTokenizer;

public class MatrixServerObjeto extends Thread {
	private MatrixOperations matrixOperations;
	private ServerSocket socket;
	private Socket connection;
	private long ioTime;
	private long processingTime;
	private String response;

	public MatrixServerObjeto() {
		matrixOperations = new MatrixOperations();
		try { socket = new ServerSocket(63000); }
		catch (IOException e) { System.out.println("IO Exception creating the socket"); }
	}

	/**
	 * Metodo run del Thread
	 */
	public void run() {
		System.out.println("MatrixServerObjeto started...");
		while(true) {
			try {
				connection = socket.accept();
				ObjectInputStream ois = new ObjectInputStream(connection.getInputStream());
				Integer[] requestData = (Integer[]) ois.readObject();
				int hilo = requestData[2];
				int size = requestData[1];
				int operation = requestData[0];
				
				System.out.println("Received a request for a " + size + "x" + size + "matrix  Thread#"+hilo);

				ObjectOutputStream out = new ObjectOutputStream(connection.getOutputStream());
				out.flush();

				long initTime = System.currentTimeMillis();
				String[][] a = matrixOperations.createRandomStringMatrix(size);
				String[][] b = matrixOperations.createRandomStringMatrix(size);
				processingTime = System.currentTimeMillis() - initTime;

				initTime = System.currentTimeMillis();
				createInputFiles(a,b,Integer.toString(size),hilo);
				ioTime = System.currentTimeMillis() - initTime;

				a = null;
				b = null;
				response = operation + "%" + size + "%";

				String[][] matrix = null;
				initTime = 0l;

				initTime = System.currentTimeMillis();
				String[][] matrix1 = loadMatrixFromFile("matrix1_" + size + "_" + hilo + ".txt", size);
				String[][] matrix2 = loadMatrixFromFile("matrix2_" + size + "_" + hilo + ".txt", size);
				ioTime += System.currentTimeMillis() - initTime;

				switch(operation) {
					case 1:
						initTime = System.currentTimeMillis();
						matrix = matrixOperations.addition(matrix1, matrix2);
						processingTime += System.currentTimeMillis()-initTime;
						response += "Suma[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
						break;
					case 2:
						initTime = System.currentTimeMillis();
						matrix = matrixOperations.substraction(matrix1, matrix2);
						processingTime += System.currentTimeMillis()-initTime;
						response += "Resta[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
						break;
					case 3:
						initTime = System.currentTimeMillis();
						matrix = matrixOperations.multiplication(matrix1, matrix2);
						processingTime += System.currentTimeMillis()-initTime;
						response += "Multiplicacion[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
						break;
					case 4:
						initTime = System.currentTimeMillis();
						matrix = matrixOperations.division(matrix1, matrix2);
						processingTime += System.currentTimeMillis()-initTime;
						response += "Division[" + size + "]: " + processingTime + " ms - Hilo#" + hilo;
						break;
				}
				initTime = System.currentTimeMillis();
				this.createOutputFile(matrix, Integer.toString(requestData[1]), hilo);
				ioTime += System.currentTimeMillis() - initTime;

				initTime = System.currentTimeMillis();
				String matrixResult = this.loadMatrixFromFileToSendResult("result_"+size+"_"+hilo+".txt", size);
				ioTime += System.currentTimeMillis() - initTime;

				response += "%" + ioTime + "%";
				response += processingTime + "%";
				response += matrixResult;

				out.writeObject(response);

				out.close();
				ois.close();
				connection.close();
			}
			catch (IOException e) { }
			catch (ClassNotFoundException e) { }
		}
	}

	/**
	 * Carga un archivo de texto en una matriz de Strings
	 * @param matrixFile 	path del archivo de texto a abrir
	 * @param size			tamaño de la matriz a llenar
	 * @return				matriz de Strings
	 */
	public String[][] loadMatrixFromFile(String matrixFile, int size){
		String[][] stringMatrix = new String[size][size];
		try {
			FileInputStream fstream = new FileInputStream(matrixFile);
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			String strLine;
			StringTokenizer tokenizer;
			int i = 0;
			while ((strLine = br.readLine()) != null) {
				tokenizer = new StringTokenizer(strLine, ",");
				if(tokenizer.countTokens() == size) {
					for (int j=0; j<size; j++)
						stringMatrix[i][j] = tokenizer.nextToken();
				}
				i++;
			}
			br.close();
			in.close();
		}
		catch(IOException ioe) { }
		return stringMatrix;
	}
	
	public String loadMatrixFromFileToSendResult(String matrixFile, int size){
		StringBuffer stringMatrix = new StringBuffer();
		try
		{
			FileInputStream fstream = new FileInputStream(matrixFile);
			
			DataInputStream in = new DataInputStream(fstream);
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			
			String strLine;
			stringMatrix.append("[");
			while ((strLine = br.readLine()) != null)
			{
				stringMatrix.append("["+ strLine + "]");
			}
			stringMatrix.append("]");
			
			br.close();
			in.close();
		}
		catch(IOException ioe) { }
		return stringMatrix.toString();
	}
	
	/**
	 * Escribe la matriz resultado en un archivo de texto
	 * @param a			La matriz resultado	
	 * @param suffix	Un sufijo para el archivo
	 */
	public void createOutputFile(String[][] a, String suffix, int hilo) {
		try {
			FileOutputStream fos = new FileOutputStream("result_"+ suffix +"_"+hilo+".txt"); 
			OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<a.length; i++) {
				for(int j=0; j<a.length; j++) {
					if(j>0)
						out.write(",");
					out.write(a[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
		}
		catch (UnsupportedEncodingException e) { }
		catch (FileNotFoundException e) { }
		catch (IOException e) { }
	}
	
	/**
	 * Escribe las matrices en archivos de texto como matrix1 y matrix2 
	 * @param a			la matriz 1
	 * @param b			la matriz 2
	 * @param suffix	Un sufijo para identificar los archivos
	 */
	public void createInputFiles(String[][] a, String[][] b, String suffix, int hilo) {
		try {
			FileOutputStream fos = new FileOutputStream("matrix1_"+suffix+"_"+hilo+".txt"); 
			OutputStreamWriter out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<a.length; i++) {
				for(int j=0; j<a.length; j++) {
					if(j>0)
						out.write(",");
					out.write(a[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
			
			fos = new FileOutputStream("matrix2_"+suffix+"_"+hilo+".txt"); 
			out = new OutputStreamWriter(fos, "UTF-8");
			for(int i=0; i<b.length; i++) {
				for(int j=0; j<b.length; j++) {
					if(j>0)
						out.write(",");
					out.write(b[i][j]);
				}
				out.write("\n");
			}
			out.close();
			fos.close();
		}
		catch (UnsupportedEncodingException e) { }
		catch (FileNotFoundException e) { }
		catch (IOException e) { }
	}

	public static void main(String...arg) {
		new MatrixServerObjeto().start();
	}
}
